/*
 * Copyright (c) 2014 pkzd/1.0, 颜文泽(robin)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     1 Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     2 Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include	<buf.h>
#include	<conf.h>
#include	<proc.h>
#include	<param.h>
#include	<string.h>

struct buf bfreelist;
struct buf buf[NBUF];
char buffers[NBUF][BLKSIZ];

int
binit(void)
{
	uint i;
	struct buf *bp;
	struct bdevsw *bdp;
	struct devtab *dp;

	bfreelist.b_forw = bfreelist.b_back = 
		bfreelist.av_forw = bfreelist.av_back = &bfreelist;
	for(i = 0; i < NBUF; i++){ 
		bp = &buf[i];
		bp->b_dev = -1;
		bp->b_flags = B_BUSY;
		bp->b_addr = buffers[i];
		bp->b_back = &bfreelist; 
		bp->b_forw = bfreelist.b_forw;
		bfreelist.b_forw->b_back = bp;
		bfreelist.b_forw = bp;
		bp->av_back = &bfreelist; 
		bp->av_forw = bfreelist.av_forw;
		bfreelist.av_forw->av_back = bp;
		bfreelist.av_forw = bp;
	}	
	nblkdev = 0;
	for(bdp = bdevsw; bdp->d_open; bdp++){
		if(dp = bdp->d_tab){
			dp->b_forw = dp;
			dp->b_back = dp;
			dp->d_actf = NULL;
			dp->d_actl = NULL;
		}
		nblkdev++;
	}
	return 0;
}

void
clrbuf(struct buf *bp)
{
	memset(bp->b_addr, 0, BLKSIZ);
}

struct buf *
getblk(dev_t dev, uint blkno)
{
	struct buf *bp;
	struct devtab *dp;

	if(dev == NODEV)
		return NULL;
	dp = bdevsw[MAJOR(dev)].d_tab;
loop:
	for(bp = dp->b_forw; bp != dp; bp = bp->b_forw){
		if(bp->b_dev != dev || bp->b_blkno != blkno)
			continue;	
		if(bp->b_flags & B_BUSY){
			bp->b_flags |= B_WANTED;
			sleep(bp, PBUF);
			goto loop;
		}
		notavail(bp);
		return bp;
	}
	if(bfreelist.av_forw == &bfreelist){
		bfreelist.b_flags |= B_WANTED;
		sleep(&bfreelist, PBUF);			
		goto loop;
	}
	notavail(bp = bfreelist.av_forw);
	if(bp->b_flags & B_DELWRI){
		bwrite(bp);
		goto loop;
	}
	bp->b_flags = B_BUSY;
	bp->b_back->b_forw = bp->b_forw;
	bp->b_forw->b_back = bp->b_back;
	bp->b_forw = dp->b_forw; 	
	bp->b_back = dp;
	dp->b_forw->b_back = bp;
	dp->b_forw = dp;
	bp->b_dev = dev;
	bp->b_blkno = blkno;
	return bp;
}

void
notavail(struct buf *bp)
{
	bp->av_back->av_forw = bp->av_forw;
	bp->av_forw->av_back = bp->av_back;
	bp->b_flags |= B_BUSY;
}

void
brelse(struct buf *bp)
{
	uint x;
	register struct buf **backp;

	x = bp->b_flags;
	if(bp->b_flags & B_ERROR){
		bp->b_dev = NODEV;
		bp->b_flags |= B_AGE;
	}
	if(bp->b_flags & B_AGE){
		backp = &bfreelist.av_forw;
		(*backp)->av_back = bp;
		bp->av_forw = *backp;
		*backp = bp;
		bp->av_back = &bfreelist;
	}else{
		backp = &bfreelist.av_back;
		(*backp)->av_forw = bp;
		bp->av_back = *backp;
		*backp = bp;
		bp->av_forw = &bfreelist;
	}
	bp->b_flags &= ~(B_WANTED | B_BUSY | B_ASYNC);
	if(x & B_WANTED) 
		wakeup(bp);
	if(bfreelist.b_flags & B_WANTED){
		bfreelist.b_flags &= ~B_WANTED;
		wakeup(&bfreelist);
	}
}

void
iowait(struct buf *bp)
{
	while((bp->b_flags & B_DONE) == 0){
		bp->b_flags |= B_WANTED;
		sleep(bp, PBIO);	
	}
}

void
iodone(struct buf *bp)
{
	bp->b_flags |= B_DONE;
	if(bp->b_flags & B_WANTED){
		bp->b_flags &= ~B_WANTED;
		wakeup(bp);
	}
}

struct buf *
bread(dev_t dev, uint blkno)
{
	struct buf *bp;

	bp = getblk(dev, blkno);
	if(bp->b_flags & B_DONE)
		return bp;
	bp->b_flags |= B_READ;
	bp->b_ide.h_addr = bp->b_addr;
	bp->b_ide.h_cnt = BLKSIZ / SECTORSIZ;
	bdevsw[MAJOR(bp->b_dev)].d_rdwr(bp);
	iowait(bp);
	bp->b_flags &= ~B_READ;
	return bp;
}

void
bwrite(struct buf *bp)
{
	bp->b_flags |= B_WRITE;
        bp->b_flags &= ~(B_READ | B_DONE | B_ERROR | B_DELWRI);
        bp->b_ide.h_addr = bp->b_addr;
        bp->b_ide.h_cnt = BLKSIZ / SECTORSIZ;
        bdevsw[MAJOR(bp->b_dev)].d_rdwr(bp);
        iowait(bp);
	bp->b_flags &= ~B_WRITE;
        brelse(bp);
}
